/*  

Cafechess Library: Cover basic chess programming needs, like GUI, Engine integration, 
FEN and PGN parsing.

                 \\|//
                 (o o)
--------------ooO-(_)-Ooo----------------------------------
Copyright (C) 2009  By Gregory A. Prentice Snoqualmie, WA
                    www.cafechess.org
                    cafechess@comcast.net
                      Oooo
--------------oooO----(  )---------------------------------
              (  )    (_/
               \_)

Microsoft Public License (Ms-PL)
               
Revisions:

Author        Date    Description:

*/
using System;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Collections;
using System.Collections.Specialized;

using Cafechess.General;

namespace Cafechess.Imaging
{
  /// <summary>
	/// Our base drawing container that is used to hold multiple IDrawable interfaces.
	/// Paints our image to an public bitmap that can later be displayed to the screen
	/// or saved to a file.
	/// </summary>
	public class Layer : Disposable, IDrawable
	{
    #region Fields
    /// <summary>
    /// Contains a list of our drawable objects.
    /// </summary>
    ArrayList coDrawable;
    /// <summary>
    /// Private member for Property IsEnabled.
    /// </summary>
    bool coIsEnabled;
    /// <summary>
    /// Private member for Property IsRedraw.
    /// </summary>
    bool coIsRedraw;
    /// <summary>
    /// Private member for Property IsDragging
    /// </summary>
    bool coIsDragging;
    /// <summary>
    /// Private member for Property IsLocked.
    /// </summary>
    bool coIsLocked;
    /// <summary>
    /// Private member for Property Name.
    /// </summary>
    string coName;
    /// <summary>
    /// Private member for Property Square.
    /// </summary>
    Rectangle coSquare;
    /// <summary>
    /// Private member for Property Parent.
    /// </summary>
    IDrawable coParent;
    /// <summary>
    /// Our private public surface that our images get rendered to.
    /// </summary>
    protected Bitmap coBitmap;
    /// <summary>
    /// Our graphics object that references our public coBitmap drawing surface.
    /// </summary>
    private Graphics coOffScreenDC;
    /// <summary>
    /// Brush used to fill our background / Transparent color.
    /// </summary>
    Brush coBackgroundBrush;
    /// <summary>
    /// Color of our background / Transparent color.
    /// </summary>
    Color coBackground;
    /// <summary>
    /// Used to set our transparencey color and opaque value.
    /// </summary>
    ImageAttributes coImageAttributes;
    /// <summary>
    /// Private member used to hold the Specialized layer.
    /// </summary>
    Layer coSpecialized;

    #endregion

    #region Properties

    /// <summary>
    /// Contains a reference to a layer object that will perform a
    /// specialized drawing action.  Normally this is where we will
    /// output drawing directly to the screen.
    /// </summary>
    public Layer Specialized
    {
      get{ return coSpecialized; }
      set{ coSpecialized = value;}
    }

    /// <summary>
    /// Used to set the dimentions of this layer.  Frees up any previous
    /// resources and creates our drawing surface.
    /// </summary>
    virtual public Rectangle Square
    {
      get{ return coSquare; }
      set
      {
        // Set the size of our surface.
        coSquare = value;
        // If we have a surface remove it.
        if(coBitmap != null)
          coBitmap.Dispose();
        // Clear our graphics object if present.
        if(coOffScreenDC != null)
          coOffScreenDC.Dispose();
        // Allocate our new surface.
        coBitmap = new Bitmap(coSquare.Width, coSquare.Height);
        // Create our new graphics object for drawing and such.
        coOffScreenDC = Graphics.FromImage(coBitmap);
        // Keep ugly outlines form appearing when drawing on our surface.
        coOffScreenDC.InterpolationMode = InterpolationMode.NearestNeighbor;
        // Tell the parent to redraw itself.
        IsRedraw = true;
      }
    }

    /// <summary>
    /// The name of this Layer.
    /// </summary>
    public string Name
    {
      get{ return coName; }
      set{ coName = value; }
    }

    /// <summary>
    /// Set the flag that determines if this object if available for
    /// drawing itself or not.
    /// </summary>
    public bool IsEnabled
    { 
      get{return coIsEnabled;} 
      set
      { 
        // When we change the state of a layer we may need to tell
        //  it's parent to redraw itself the next chance it gets.
        if(coIsEnabled != value && Parent != null )
          Parent.IsRedraw = true;
        // Set variable.
        coIsEnabled = value;
      }
    }
    /// <summary>
    /// Normally used to signal that we are in the middle of a drag and drop opperation
    /// in this layer.
    /// </summary>
    public bool IsDragging
    {
      get{return coIsDragging;}
      set{coIsDragging = value;}
    }
    /// <summary>
    /// Return the number of drawable objects this layer is currently
    /// holding.
    /// </summary>
    public int Count{ get{ return coDrawable.Count; } }
    /// <summary>
    /// Used to signal the layer to redraw itself the next chance it gets.
    /// </summary>
    public bool IsRedraw
    { 
      get{ return coIsRedraw; } 
      set{ coIsRedraw = value;} 
    }
    /// <summary>
    /// Used to lock the state of this layer from any changes or movement.
    /// </summary>
    public bool IsLocked{ get{ return coIsLocked; } set{ coIsLocked = value;} }
    /// <summary>
    /// Used to signal that we must perform a special condition render of the object.
    /// Normally this is used to output an image directly to the screen or another
    /// graphics object.
    /// </summary>
    public bool IsSpecialDraw{ get{return coSpecialized!=null;}}
    
    public Bitmap Drawing
    {
      get{ return coBitmap; }
    }

    #endregion 

    #region Constructors
    /// <summary>
    /// Default constructor which set an initial square of 20x20
    /// and a background color (transparency) of Magenta.
    /// </summary>
    public Layer()
		{
      Initialize(Color.Magenta);
      Square = new Rectangle(0,0,20,20);
    }
    /// <summary>
    /// Constructor allows you to specify the initial background (transparency)
    /// color and the size of the image.
    /// </summary>
    /// <param name="square"></param>
    /// <param name="background"></param>
    public Layer(Rectangle square, Color background)
    {
      Initialize(background);
      Square = square;      
    }
    /// <summary>
    /// Common initialization code used by all constructors.
    /// </summary>
    /// <param name="background"></param>
    private void Initialize(Color background)
    {
      coBackground = background;
      coBackgroundBrush = new SolidBrush(background);
      IsEnabled = true;
      coIsRedraw = true;
      coDrawable = new ArrayList();
      coImageAttributes = new ImageAttributes();
      coImageAttributes.SetColorKey(coBackground, coBackground);
    }
    #endregion Constructors

    #region Methods
    /// <summary>
    /// Used to clean up and Managed resource.
    /// </summary>
    override protected void DisposeManaged()
    {
      if( ! IsDisposed )
      {
      }
    }
    /// <summary>
    /// Used to clean up any unmmanaged resources.
    /// </summary>
    override protected void DisposeUnmanaged()
    {
      if( ! IsDisposed )
      {
        Clear();
        coDrawable = null;
        if( coImageAttributes != null )
        {
          coImageAttributes.Dispose();
          coImageAttributes = null;
        }
        if( coBitmap != null )
        {
          coBitmap.Dispose(); 
          coBitmap = null;
        }
        if( coBackgroundBrush != null )
        {
          coBackgroundBrush.Dispose();
          coBackgroundBrush = null;
        }
      }
    }

    /// <summary>
    /// Remove all IDrawable objects from our layer, calling dispose on each object.
    /// </summary>
    public void Clear()
    {
      if( coDrawable != null )
      {
        foreach(IDrawable idraw in coDrawable)
        {
          idraw.Dispose();
        }
        coDrawable.Clear();
      }
      IsRedraw = true;
      if(Parent != null)
        Parent.IsRedraw = true;
    }

    /// <summary>
    /// Only removes unlocked layers.
    /// </summary>
    public void ClearUnlocked()
    {
      if(coDrawable != null)
      {
        int removeIndex=0;
        for(int index=0; index < coDrawable.Count; index++)
        {
          IDrawable idraw = coDrawable[removeIndex] as IDrawable;
          if( idraw.IsLocked == false )
          {
            idraw.Dispose();
            coDrawable.RemoveAt(index);
          }
          else
          {
            // Didn't remove this so keep going.
            removeIndex++;
          }
        }
      }
    }

    /// <summary>
    /// Adds a drawable item to our layer.  Uses the name of the IDrawable
    /// interface to ensure only one with this name is added.
    /// </summary>
    /// <param name="iDraw"></param>
    /// <returns>True if added to the layer, false means an item by this name exists</returns>
    public void Add(IDrawable idraw)
    {
      IsRedraw = true;
      idraw.Parent = this;
      coDrawable.Add(idraw);      
    }

    /// <summary>
    /// Removes an item from the layer by specific index.  Check to make sure
    /// one exists before removing it.
    /// </summary>
    /// <param name="index"></param>
    public void Remove(int index)
    {
      IDrawable idraw = Get(index);
      if(idraw != null )
      {
        idraw.Dispose();
        coDrawable.RemoveAt(index);
      }
    }
    /// <summary>
    /// Removes all items from the layer that match a given name.
    /// </summary>
    /// <param name="name"></param>
    public void Remove(string name)
    {
      if(coDrawable != null)
      {
        int removeIndex=0;
        for(int index=0; index < coDrawable.Count; index++)
        {
          IDrawable idraw = coDrawable[removeIndex] as IDrawable;
          if( idraw.Name.Equals(name) )
          {
            idraw.Dispose();
            coDrawable.RemoveAt(index);          
          }
          else
          {
            // didn't remove so keep going.
            removeIndex++;
          }
        }
      }
    }
    /// <summary>
    /// Put a new IDrawable object at a specific location in our Layer.
    /// </summary>
    /// <param name="index"></param>
    /// <param name="idraw"></param>
    public void Insert(int index, IDrawable idraw)
    {
      IsRedraw = true;
      coDrawable.Insert(index, idraw);
    }
    /// <summary>
    /// Move the referenced object closer to the beginning of our
    /// list as these items get painted first.
    /// </summary>
    /// <param name="ndx"></param>
    public void Push(int ndx)
    {
      if( coDrawable.Count > 1 && ndx < coDrawable.Count && ndx > 0 )
      {
        coDrawable.Reverse(ndx-1,2);
        IsRedraw = true;
      }
    }
    /// <summary>
    /// Move the referneced object further from the beginning of our
    /// list as these items get painted later.
    /// </summary>
    /// <param name="ndx"></param>
    public void Pop(int ndx)
    {
      if( (coDrawable.Count > 1) && 
        (ndx < coDrawable.Count - 2) && 
        (ndx >= 0) )
      {
        coDrawable.Reverse(ndx,2);
        IsRedraw = true;
      }
    }

    /// <summary>
    /// Move the referenced objed to a new location our list
    /// </summary>
    /// <param name="layerOne"></param>
    /// <param name="layerTwo"></param>
    public void Move(int layerOne, int layerTwo)
    {
      if(
         layerOne < coDrawable.Count && 
         layerTwo < coDrawable.Count &&
         layerOne != layerTwo 
        )
      {
        coDrawable.Reverse(layerOne,layerTwo);
        IsRedraw = true;
      }
    }

    /// <summary>
    /// Retrieves the requested IDrawable object.
    /// </summary>
    /// <param name="ndx"></param>
    /// <returns></returns>
    public IDrawable Get(int ndx)
    {
      return (IDrawable) coDrawable[ndx];
    }
    /// <summary>
    /// Asks the layer to redraw itself to the public Bitmap if it needs to.
    /// </summary>
    public void Render()
    {
      // Has someone updated the image and needs it to render itself?
      if( IsRedraw )
      {
        // Blank out the current image.
        coOffScreenDC.FillRectangle(coBackgroundBrush,0,0,coSquare.Width,coSquare.Height);
        // Get each drawing item.
        foreach( IDrawable idraw in coDrawable) 
        {
          idraw.Render(coOffScreenDC);
        }
        IsRedraw = false;
      }
    }

    #endregion Methods

    #region IDrawable Members
    
    /// <summary>
    /// Returns true if the request point reflects a positive
    /// hit on this layer.
    /// </summary>
    /// <param name="point"></param>
    /// <returns></returns>
    public bool HitTest(Point point)
    {
      return Square.Contains(point);
    }

    /// <summary>
    /// Retrieves the IDrawable interfacs of the object that owns this layer.
    /// </summary>
    public IDrawable Parent
    { get{return coParent;} set{coParent=value;}}
    /// <summary>
    /// Paints this layer to the supplied graphics object.  Normally the screen.
    /// </summary>
    /// <param name="graphics"></param>
    public void Render(System.Drawing.Graphics graphics)
    {
      // Don't event draw the object unless it is 
      if(IsEnabled )
      {
        // Don't call Render if we are in specilized rendering.
        if(!IsSpecialDraw)
        {
          // This code preforms the accutal request for each item in the layer to
          //  render itself to the public bitmap.
          Render();          
        }
        // Out put the oritinal image directly to our graphics
        graphics.DrawImage(coBitmap, coSquare,0,0,coSquare.Width,coSquare.Height,GraphicsUnit.Pixel,coImageAttributes);
        // Call the specialised code here to perform additional drawing.  Normally 
        //  for drag and drop opperations of a graphics image.
        if(IsSpecialDraw)
        {
          Specialized.RenderSpecialized(graphics);
        }

      }
    }
    /// <summary>
    /// Returns an enumerator to allow us to move through all of the IDrawable objects that
    /// this layer owns.
    /// </summary>
    /// <returns></returns>
    public IEnumerator GetEnumerator()
    { 
      return coDrawable.GetEnumerator();
    }
    #endregion

    /// <summary>
    /// Provide an specialized painting such as a specific public object to the supplied
    /// graphics object, normally for direct output to the screen for drag and drop effects.
    /// </summary>
    /// <param name="graphics"></param>
    virtual public void RenderSpecialized(System.Drawing.Graphics graphics)
    {
      graphics.DrawImage(coBitmap,Square);
    }
    /// <summary>
    /// Retrieves a string representing the drawable objects currently being displayed.
    /// </summary>
    /// <returns></returns>
    override public string ToString()
    {
      StringBuilder builder = new StringBuilder();
      bool delimit = false;
      foreach(IDrawable idraw in coDrawable)
      {
        if(delimit)
          builder.Append("/");
        else
          delimit = true;
        builder.Append(idraw.Name);
      }
      return builder.ToString();
    }
    /// <summary>
    /// Used to rebuild the layers objects from a text string.
    /// </summary>
    /// <param name="str"></param>
    virtual public void FromString(string str){}
  }
}
